import { extend, isChanged, isObject } from '../shared/index'
import { trackEffect, triggerEffect } from './effect'
import { reactive } from './reactive';

/**
 * 抽象一个 Ref 实现类
 */
class RefImpl {
    private _value;
    private _nowValue;
    /**
     * 给Ref 对象一个 标识，标识这个对象为 ref对象，即：isRef => true
     */
    public _v_isRef = true;
    public deps: any = new Set<any>;

    constructor(value) {
        this._value = value;
    }

    /**
     * get 访问器，该访问器实现 依赖收集
     */
    get value() {
        // 收集依赖
        trackEffect(this.deps);
        return this._value;
    }

    /**
     * set 访问器，该访问器实现 依赖调用
     */
    set value(newVal) {
        if (!isChanged(this._nowValue, newVal)) return;

        // 先设置更新 value 值
        this._value = newVal;
        this._nowValue = newVal;
        // 触发依赖
        triggerEffect(this.deps);
    }
}


export function ref(value) {
    // 如果传入的 ref 对象是一个对象，则直接返回一个 reactive 对象
    if (isObject(value))
        return new RefImpl(reactive(value));

    return new RefImpl(value);
}


/**
 * 检查一个对象是否为 ref 响应式数据
 * @param refObj ref 或者 其他类型数据
 * @returns 返回传入对象是否为一个 ref 响应式数据，true 是 false 不是
 */
export function isRef(refObj) {
    // 双重取反返回是否为 ref 标记
    // 如果这个对象不是 ref，自然是 undefined， 双重取反后的返回值就是 false
    return !!refObj._v_isRef;
}

/**
 * 拆解（拆箱）一个 ref 对象，仅获取 ref 对象的数值
 * @param refObj ref 或者 其他类型数据
 * @returns 如果传入对象为 ref 响应式数据，返回 value 值， 如果是其他类型数据，返回传入对象本身
 */
export function unRef(refObj) {
    return isRef(refObj) ? refObj.value : refObj;
}


/**
 * 返回一个 Proxy 代理对象，该代理对象进行读取 ref 类型属性时，会进行 unRef 操作，达到不需要 .value 访问目的
 * 同时，该对象在进行 set 操作时，会处理 ref 类型数据，如果原来的属性为ref ，新值为普通数据，会保留该属性的 ref 特性，将数值类型装入ref .value 属性中
 */
export function proxyRefs(objectWithRefs) {
    return new Proxy(objectWithRefs, {
        get(target, key) {
            // 使用 unRef 对返回的数据进行拆解，这样 ref 类型的数据就会被拆解为 value 部分
            return unRef(Reflect.get(target, key));
        },
        set(target, key, value) {
            // 如果原始值是一个 ref 类型，新的值不是一个 ref 类型，则需要对原始值的 ref.value 进行赋值
            if (isRef(target[key]) && !isRef(value)) {
                return target[key].value = value;
            }
            // 其他情况直接修改代理对象的属性值
            const res = Reflect.set(target, key, value);
            return res;
        },
    })
}